package Execute.MiniSQL.module;

import Execute.MiniSQL.SqlResult;
import Execute.MiniSQL.TableManager;
import Execute.MiniSQL.Transformer;

import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class Update {
    public String tableName;        // 表名
    public String condition;        // 条件，"colA_=_25,_colB_=_'2' where columnB_=_'abc'_,_colC_>_12"
    public Map<String, String> map; // 存储表结构 <列名称，数据类型>
    public Map<String, String> userMap; // 存储判别条件 <列名称， 操作符_值>
    public Map<String, String> setMap;  // 存储修改条件 <列名称， 值>  操作符为= 不用存
    public int singleLen;           // 单个数据的长度（一行
    public SqlResult sqlResult;
    public LinkedList<Integer> list_stringLen;  // 字符型数据占长度

    public Update(String tableName, String sql) {
        this.tableName = tableName;
        this.condition = sql;
        this.map = new LinkedHashMap<>();       // 采用LinkedHashMap维护顺序，后面根据这个顺序来确定midb文件中列数据类型的顺序，从而确定读取的字节数
        this.userMap = new LinkedHashMap<>();
        this.setMap = new LinkedHashMap<>();
        this.list_stringLen = new LinkedList<>();   // 记录字符型数据的长度（因为当一个表中有多个同类型数据时，int和double所占字节固定，而varchar所占字节需要按照用户输入的数字确定，即非定长）
        this.singleLen = 0;

        // 执行删除操作
        if(check()) {
            updateFunc();
        }
    }

    /*
    * 该方法负责进行预处理。
    * 1、判断给出的条件 列名称、数据类型是否可用
    * 2、将相应的信息存入对应的变量（map、userMap、setMap、list_stringLen、singLen...)
    * */
    public boolean check() {
        // 先获取表结构
        String tableStruct = TableManager.preDesc(tableName);   // tableStruct = "Student id int Height decimal name varchar(20)"
        String[] ts = tableStruct.split(" ");

        // 计算表中一个数据的长度  int 4个字节 double 8个字节 字符varchar(a) 3a个字节
        for(int i = 1; i < ts.length; i += 2){
            // 将列名称和数据类型存入map
            map.put(ts[i], ts[i+1]);
            if (ts[i + 1].equalsIgnoreCase("int")) {
                // 整型占4个字节
                singleLen += 4;
            } else if (ts[i + 1].equalsIgnoreCase("decimal")) {
                // double占8个字节
                singleLen += 8;
            } else {
                // varchar(a) 占3a个字节
                int l = TableManager.getVarchar(ts[i + 1]);
                l = 3 * l;
                list_stringLen.add(l);  // 存入链表
                singleLen += l;
            }
        }

        // 提取条件
        String modify_str = condition.substring(0, condition.indexOf("where")).trim();  // modify_str = "colA_=_25_,_colB_=_'2'"
        String search_str = condition.substring(condition.indexOf("where ")+6).trim();  // search_str = "colB_=_'abc'_,_colC_>_12"

        String[] search = search_str.split(",");        // "colB_?_x_,_colC_?_x" --> "colB_?_x_" "_colC_?_x"
        String[] modify = modify_str.split(",");        // "colA_?_x_,_colB_?_x" --> "colA_?_x_" "_colB_?_x"
        // userMap 提取判别信息
        for(String s: search) {
            s = s.trim();       // s = "colA_?_x"
            s = s.replaceAll("\\s+","");    // s = "colA?x"
            // 提取操作符
            String oper = null;
            if (s.contains(">=")) oper = ">=";
            else if(s.contains("<=")) oper = "<=";
            else if(s.contains("<>")) oper = "<>";
            else if(s.contains(">")) oper = ">";
            else if(s.contains("<")) oper = "<";
            else if(s.contains("=")) oper = "=";

            // 提取列名
            String colName = s.substring(0, s.indexOf(oper));       // colName = "colA"
            // 提取值
            String value;
            if(oper.length() == 1)
                value = s.substring(s.indexOf(oper) + 1);          // value = "x"
            else
                value = s.substring(s.indexOf(oper) + 2);         // value = "x"

            // 列名是否存在
            if(map.get(colName) == null) {
                System.out.println(colName + "：您输入的列名在不存在！");
                this.sqlResult = new SqlResult(colName + "：您输入的列名在不存在！", null);
                return false;
            }

            // 值类型输入是否正确
            String type = map.get(colName);
            switch (type) {
                case "int" :
                    try {
                        int linshi = Integer.parseInt(value);
                    }catch (Exception e) {
                        System.out.println(s + ": 输入需要为一个整数。");

                        return false;
                    }
                    break;
                case "decimal" :
                    try {
                        double linshi = Double.parseDouble(value);
                    }catch (Exception e) {
                        System.out.println(s + ": 输入需要为一个小数。");
                        return false;
                    }
                    break;
                default:
                    if((value.startsWith("'") & ! value.endsWith("'")) | (!value.startsWith("'") & value.endsWith("'"))) {
                        System.out.println(s + ": 字符型数据您可以不输入分号或输入2个分号。");
                        return false;
                    }
                    break;
            }

            // 表名、列名存在，可以执行删除
            // 将检索的信息存入userMap
            userMap.put(colName,oper+" "+value);

        }

        // setMap 提取修改信息
        for(String s: modify) {
            s = s.trim();           // s = "colA_?_x"
            s = s.replaceAll("\\s+", "");       // s = "colA?x"
            // 提取操作符
            String oper = "=";
            // 提取列名
            String colName = s.substring(0, s.indexOf(oper));       // colName = "colA"
            // 提取值
            String value = s.substring(s.indexOf(oper)+1);          // value = "x"

            if(map.get(colName) == null) {
                System.out.println(colName + "：您输入的列名在不存在！");
                this.sqlResult = new SqlResult(colName + "：您输入的列名在不存在！", null);
                return false;
            }

            // 检查值输入是否正确
            String type = map.get(colName);
            switch (type) {
                case "int" :
                    try {
                        int linshi = Integer.parseInt(value);
                    }catch (Exception e) {
                        System.out.println(s + ": 输入需要为一个整数。");

                        return false;
                    }
                    break;
                case "decimal" :
                    try {
                        double linshi = Double.parseDouble(value);
                    }catch (Exception e) {
                        System.out.println(s + ": 输入需要为一个小数。");
                        return false;
                    }
                    break;
                default:
                    if((value.startsWith("'") & ! value.endsWith("'")) | (!value.startsWith("'") & value.endsWith("'"))) {
                        System.out.println(s + ": 字符型数据您可以不输入分号或输入2个分号。");
                        return false;
                    }
                    break;
            }

            // 表名、列名存在，可以执行删除
            // 将修改的信息存入setMap
            setMap.put(colName, value);
        }

        return true;
    }

    public void updateFunc() {
        String path = TableManager.DATA_PATH + "/" + tableName + ".midb";
        try {
            RandomAccessFile raf = new RandomAccessFile(path, "rw");
            long LEN = raf.length();        // 文本总长度
            long nowPos = 0;             // 当前指针位置
            // 一次读取长度
            byte[] bytes_int = new byte[4];
            byte[] bytes_double = new byte[8];
            byte[] bytes_varchar;

            boolean[] ismodify = new boolean[userMap.size()];
            int pos = 0;                    // 当前 ismodify[] 下标

            long[] modifypos = new long[(int)LEN/(singleLen+2)];    // 记录要修改的数据的头（指针）位置
            Arrays.fill(modifypos, -1); // 初始值设为-1，视为不存在
            int modifyNum = 0;              // 修改数据的数量(同时可作为modifypos数组的下标)

            // 读取文件内容判断数据是否要修改，直到指针位置到达文本末尾
            Object num_int = null, num_dou = null, s = null;
            while(nowPos < LEN) {
                // 每个数据开始前将ismodify数组初始为false, pos、k初始为0
                Arrays.fill(ismodify, Boolean.FALSE);
                pos = 0;
                int k = 0;      // list_stringLen的下标
                // 读取一行数据的操作
                // 遍历map，得到各个列的数据类型
                for(Map.Entry<String, String> entry : map.entrySet()) {
                    if (entry.getValue().equalsIgnoreCase("int")) {

                        raf.read(bytes_int);
                        nowPos += 4;
                        num_int = Transformer.bytesToInt(bytes_int);

                        // 该项是否是判断删除的条件，是的话判断是否满足删除条件
                        if(whetherModify(entry.getKey(), num_int)) {
                            ismodify[pos] = true;
                            pos++;
                        }

                        System.out.print(num_int + ", ");

                    } else if (entry.getValue().equalsIgnoreCase("decimal")) {

                        raf.read(bytes_double);
                        nowPos += 8;
                        num_dou = Transformer.bytesToDouble(bytes_double);

                        // 该项是否是判断删除的条件，是的话判断是否满足删除条件
                        if(whetherModify(entry.getKey(), num_dou)) {
                            ismodify[pos] = true;
                            pos++;
                        }

                        System.out.print(num_dou + ", ");

                    } else {

                        bytes_varchar = new byte[list_stringLen.get(k)];
                        raf.read(bytes_varchar);
                        nowPos += list_stringLen.get(k);
                        k++;
                        s = new String(bytes_varchar, StandardCharsets.UTF_8).trim();

                        // 该项是否是判断删除的条件，是的话判断是否满足删除条件
                        if(whetherModify(entry.getKey(), s)) {
                            ismodify[pos] = true;
                            pos++;
                        }

                        System.out.print(s + ", ");

                    }
                }

                // 判断是否要修改，即检查ismodify数组值是否全为真
                // 因为前面是顺序赋值的，所以检查最后一个值是否为true即可
                if (ismodify[ismodify.length - 1]) {
                    // 确定修改
                    System.out.print(" \t该数据确定修改");
                    modifypos[modifyNum] = (nowPos - singleLen);
                    modifyNum++;
                }
                System.out.print("\n");

                // 下一行
                raf.skipBytes(2);   // 跳过换行符
                nowPos += 2;
            }

            // 再次读取文件，将标记的元素修改
            if(modifyNum > 0) {
                // 执行修改操作
                System.out.println("执行修改操作");
                raf.seek(0);    // 将指针位置指向文件头
                ArrayList<byte[]> bytes_all = new ArrayList<>();

                int i = 0;          // 记录修改数据位置数组 modifypos 的下标
                nowPos = 0;         // 当前指针位置

                while(i < modifyNum) {
                    // 如果不是要修改的位置，跳过
                    while(nowPos != modifypos[i]) {
                        raf.skipBytes(singleLen + 2);
                        nowPos += (singleLen + 2);
                    }
                    // 到了要修改的位置，执行修改操作

                    // 当前指针位置在行首、顺序读取并修改
                    // 循环执行以下内容直到到达行尾
                    //      检查该列是否要修改
                    //          不修改，跳过
                    //          修改，生成对应数据的字节数组，写入即可（会直接覆盖）

                    // 用户可以输入多个赋值（可以是无序的），所以程序要做的就是按顺序去赋值
                    // 我们存入的map数组是有序的，通过遍历map数组中存放列名在setMap中是否有对于key值即可
                    int k = 0;      // list_stringLen的下标
                    for(Map.Entry<String, String> entry : map.entrySet()) {
                        // 该列数据是否需要修改（列名是否在setMap中）
                        if(setMap.get(entry.getKey()) != null) {
                            // 需要修改
                            if(entry.getValue().equalsIgnoreCase("int")) {

                                int linshi = Integer.parseInt(setMap.get(entry.getKey()));
                                byte[] bytes_update = Transformer.intToBytes(linshi);
                                raf.write(bytes_update);

                            } else if (entry.getValue().equalsIgnoreCase("decimal")) {

                                double linshi = Double.parseDouble(setMap.get(entry.getKey()));
                                byte[] bytes_update = Transformer.doubleToBytes(linshi);
                                raf.write(bytes_update);

                            } else {
                                // list_stringLen里存放的是字符型数据的长度
                                String str1 = setMap.get(entry.getKey());
                                // 去除分号
                                if (str1.startsWith("'")) {
                                    str1 = str1.substring(1, str1.length()-1);
                                }
                                byte[] byte_update = str1.getBytes(StandardCharsets.UTF_8);
                                while (byte_update.length != list_stringLen.get(k)) {
                                    str1 += " ";
                                    byte_update = str1.getBytes(StandardCharsets.UTF_8);
                                }
                                raf.write(byte_update);
                                k++;
                            }

                        } else {
                            // 不需要修改，跳过
                            if(entry.getValue().equalsIgnoreCase("int")) {
                                raf.skipBytes(4);
                            } else if (entry.getValue().equalsIgnoreCase("decimal")) {
                                raf.skipBytes(8);
                            } else {
                                // list_stringLen里存放的是字符型数据的长度
                                raf.skipBytes(list_stringLen.get(k));
                                k++;
                            }
                        }
                    }

                    raf.skipBytes(2);   // 跳过换行符，开始判断下一行数据
                    nowPos += (singleLen + 2);
                    i++;

                }
            }
            raf.close();
            System.out.println("共修改 " + modifyNum + " 条数据。");
            this.sqlResult = new SqlResult("共修改 " + modifyNum + " 条数据。", null);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /*
    * 判断该值是否满足修改条件
    * 文件中该数据的 colName 列名称 value 列值
    * userMap 用户输入的删除条件 <列名称，操作符_值>
    * */
    public boolean whetherModify(String colName, Object value) {
        for(Map.Entry<String, String> entry : userMap.entrySet()) {
            // 列名是否在判断条件中
            if (entry.getKey().equalsIgnoreCase(colName)) {
                // 存在，判断是否符合条件
                String str = entry.getValue();      // str = "oper_value"
                // 提取操作符和值
                String oper = str.substring(0, str.indexOf(" "));
                String val = str.substring(str.indexOf(" ")+1);
                // 后缀1是数据，后缀2是判断条件
                int int1 = 0,int2 = 0;
                double dou1 = 0,dou2 = 0;
                String s1 = null,sign;
                if (value instanceof Integer) {
                    int1 = (int)value;
                    int2 = Integer.parseInt(val);
                    sign = "int";
                } else if (value instanceof Double) {
                    dou1 = (double) value;
                    dou2 = Double.parseDouble(val);
                    sign = "double";
                } else {
                    s1 = (String) value;
                    if(val.startsWith("'") & val.endsWith("'"))
                        val = val.substring(1, val.length()-1);     // 去除引号
                    sign = "str";
                }

                switch (oper){
                    case ">":

                        if (sign.equals("int")) return int1 > int2;
                        else if(sign.equals("double")) return dou1 > dou2;
                        else return s1.compareTo(val) > 0;

                    case "<":

                        if (sign.equals("int")) return int1 < int2;
                        else if(sign.equals("double")) return dou1 < dou2;
                        else return s1.compareTo(val) < 0;

                    case "=":

                        if (sign.equals("int")) return int1 == int2;
                        else if(sign.equals("double")) return dou1 == dou2;
                        else return s1.compareTo(val) == 0;

                    case ">=":

                        if (sign.equals("int")) return int1 >= int2;
                        else if(sign.equals("double")) return dou1 >= dou2;
                        else return s1.compareTo(val) >= 0;

                    case "<=":

                        if (sign.equals("int")) return int1 <= int2;
                        else if(sign.equals("double")) return dou1 <= dou2;
                        else return s1.compareTo(val) <= 0;

                    case "<>":

                        if (sign.equals("int")) return int1 != int2;
                        else if(sign.equals("double")) return dou1 != dou2;
                        else return s1.compareTo(val) != 0;

                    default:
                        System.out.println("修改失败，请重试！");
                        this.sqlResult = new SqlResult("修改失败，请重试！", null);
                        return false;
                }

            }
        }
        return false;
    }
}
